from IPython.display import display
from bokeh.plotting import output_notebook
from bokeh.plotting import show
import geopandas as gpd
from shapely.geometry import Point
from shapely.ops import linemerge
from gdf2bokeh import Gdf2Bokeh
from osmgt import OsmGt
from graph_tool.topology import shortest_path
output_notebook()
location = "Roanne"
%%time
pois_gdf = OsmGt.pois_from_location(location).get_gdf()
display(pois_gdf.head(2))
2020-12-11 18:51:04 - OsmGtPoi - INFO : From location: Roanne 2020-12-11 18:51:04 - OsmGtPoi - INFO : Loading data... 2020-12-11 18:51:05 - OsmGtPoi - INFO : NominatimApi: Query 200:OK in 0.32 sec. 2020-12-11 18:51:06 - OsmGtPoi - INFO : OverpassApi: Query 200:OK in 1.52 sec. 2020-12-11 18:51:06 - OsmGtPoi - INFO : Formating data 2020-12-11 18:51:06 - OsmGtPoi - INFO : Prepare GeoDataframe 2020-12-11 18:51:06 - OsmGtPoi - INFO : GeoDataframe Ready
| addr:postcode | amenity | atm | change_machine | name | opening_hours | operator | phone | ref:FR:LaPoste | source | ... | type:FR:FINESS | service:bicycle:Bicycle_Sales_and_Service | service:bicycle:repair | check_date:opening_hours | contact:email | contact:facebook | source:opening_hours | clothes | waste | geometry | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 42300 | post_office | yes | yes | Roanne Principal | Mo,We-Fr 08:30-18:00; Tu 08:30-12:00,13:45-18:... | La Poste | 3631 | 07916A | data.gouv.fr:LaPoste - 10/2012 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | POINT (4.07225 46.04071) |
| 1 | NaN | place_of_worship | NaN | NaN | Chapelle Jean Puy | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | POINT (4.07073 46.03766) |
2 rows × 128 columns
CPU times: user 83.9 ms, sys: 12.6 ms, total: 96.5 ms Wall time: 1.92 s
%%time
roads_initialized = OsmGt.roads_from_location(
location,
mode="vehicle",
additional_nodes=pois_gdf
)
roads_gdf = roads_initialized.get_gdf()
display(roads_gdf.head(2))
2020-12-11 18:51:06 - OsmGtRoads - INFO : From location: Roanne 2020-12-11 18:51:06 - OsmGtRoads - INFO : Loading data... 2020-12-11 18:51:07 - OsmGtRoads - INFO : NominatimApi: Query 200:OK in 0.32 sec. 2020-12-11 18:51:09 - OsmGtRoads - INFO : OverpassApi: Query 200:OK in 2.09 sec. 2020-12-11 18:51:09 - OsmGtRoads - INFO : Rebuild network data 2020-12-11 18:51:09 - OsmGtRoads - INFO : Network cleaning... 2020-12-11 18:51:09 - OsmGtRoads - INFO : Starting: Adding new nodes on the network 2020-12-11 18:51:09 - OsmGtRoads - INFO : Find nearest line for each node 2020-12-11 18:51:09 - OsmGtRoads - INFO : Split line 2020-12-11 18:51:09 - OsmGtRoads - INFO : Topology lines checker: to add: 267, to split: 264 2020-12-11 18:51:09 - OsmGtRoads - INFO : Starting: Find intersections 2020-12-11 18:51:09 - OsmGtRoads - INFO : Done: Find intersections 2020-12-11 18:51:09 - OsmGtRoads - INFO : Build lines 2020-12-11 18:51:09 - OsmGtRoads - INFO : Prepare GeoDataframe 2020-12-11 18:51:10 - OsmGtRoads - INFO : GeoDataframe Ready
| highway | lanes:forward | maxspeed | name | oneway | ref | id | osm_url | topo_uuid | topology | ... | EntityHand | SubClasses | alt_name | bicyle | turn:lanes | source:name | maxspeed:backward | maxspeed:forward | incline | geometry | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | primary | 2 | 50 | Rue de Charlieu | yes | D 482 | 24035569 | https://www.openstreetmap.org/way/24035569 | 1_forward | unchanged | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (4.08544 46.05234, 4.08546 46.05248... |
| 1 | primary | NaN | 50 | Rue de Charlieu | yes | D 482 | 24035570 | https://www.openstreetmap.org/way/24035570 | 2_forward | unchanged | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | LINESTRING (4.09087 46.06639, 4.09084 46.06622... |
2 rows × 59 columns
CPU times: user 613 ms, sys: 41.3 ms, total: 655 ms Wall time: 3.25 s
%%time
layers_to_add = [
{
"input_gdf": roads_gdf,
"legend": "roads",
"color": "black",
},
{
"input_gdf": pois_gdf,
"legend": "POIs",
"style": "square",
"color": "blue",
"size": 9
},
]
my_map = Gdf2Bokeh(
"My roads and POIs - from OsmGT (https://github.com/amauryval)",
layers=layers_to_add
)
show(my_map.figure)
CPU times: user 1.55 s, sys: 38.5 ms, total: 1.59 s Wall time: 1.59 s
%%time
roads_topology_gdfs = roads_initialized.topology_checker()
lines_unchanged = roads_topology_gdfs["lines_unchanged"]
lines_added = roads_topology_gdfs["lines_added"]
lines_split = roads_topology_gdfs["lines_split"]
nodes_added = roads_topology_gdfs["nodes_added"]
intersections_added = roads_topology_gdfs["intersections_added"]
layers_to_add = [
{
"input_gdf": lines_unchanged,
"legend": "roads unchanged",
"color": "black",
},
{
"input_gdf": lines_added,
"legend": "roads added",
"color": "green",
},
{
"input_gdf": lines_split,
"legend": "roads split",
"color": "orange",
},
{
"input_gdf": intersections_added,
"legend": "Intersections added",
"color": "brown",
},
{
"input_gdf": nodes_added,
"legend": "Nodes added", # POIs here
"style": "square",
"color": "blue",
"size": 9
},
]
my_map = Gdf2Bokeh(
"Topology about my roads and POIs - from OsmGT (https://github.com/amauryval)",
layers=layers_to_add
)
show(my_map.figure)
2020-12-11 18:51:11 - OsmGtRoads - INFO : Prepare topology data 2020-12-11 18:51:11 - OsmGtRoads - INFO : GeoDataframe Ready
CPU times: user 2.25 s, sys: 10.7 ms, total: 2.27 s Wall time: 2.26 s
%%time
graph = roads_initialized.get_graph()
# a plot method has been added on OsmGT.
graph.plot()
2020-12-11 18:51:14 - OsmGtRoads - INFO : Prepare graph 2020-12-11 18:51:14 - OsmGtRoads - INFO : Graph to image
CPU times: user 9.91 s, sys: 42.5 ms, total: 9.95 s Wall time: 6.28 s
%%time
# now, we have to define a start point and a end point and get their wkt
start_node_topo_uuid = 47
end_node_topo_uuid = 63
# 'topo_uuid' is generated by osmgt (during the topology processing).
# Some roads has been split that's whyso this id has been created.
start_node_gdf = pois_gdf[pois_gdf['topo_uuid'] == start_node_topo_uuid]
end_node_gdf = pois_gdf[pois_gdf['topo_uuid'] == end_node_topo_uuid]
start_node_wkt = start_node_gdf.iloc[0]["geometry"].wkt
end_node_wkt = end_node_gdf.iloc[0]["geometry"].wkt
# the graph have some methods (graph-tools method always exists!) to find egdes, vertices... Let's use the .find_vertex_from_name(). the wkt is the vertex name...
source_vertex = graph.find_vertex_from_name(start_node_wkt)
target_vertex = graph.find_vertex_from_name(end_node_wkt)
# shortest path computing...
path_vertices, path_edges = shortest_path(
graph,
source=source_vertex,
target=target_vertex,
weights=graph.edge_weights # weigth is based on line length
)
# get path by using edge names
roads_ids = [
graph.edge_names[edge]
for edge in path_edges
]
roads_gdf_copy = roads_gdf.copy(deep=True)
shortest_path_found = roads_gdf_copy[roads_gdf['topo_uuid'].isin(roads_ids)].to_crs(3857)['geometry'].to_list()
shortest_path_found_gdf = gpd.GeoDataFrame(index=[0], crs="EPSG:3857", geometry=[linemerge(shortest_path_found)])
layers_to_add = [
{
"input_gdf": shortest_path_found_gdf,
"legend": "shortest_path",
"color": "red",
"line_width": 5
},
{
"input_gdf": start_node_gdf,
"legend": "source node",
"color": "blue",
"style": "circle",
"size": 9
},
{
"input_gdf": end_node_gdf,
"legend": "target node",
"color": "green",
"style": "circle",
"size": 9
},
]
my_shortest_path_map = Gdf2Bokeh(
"My shortest path - from OsmGT (https://github.com/amauryval)",
layers=layers_to_add
)
show(my_shortest_path_map.figure)
CPU times: user 248 ms, sys: 8.11 ms, total: 256 ms Wall time: 256 ms
%%time
start_node_topo_uuid = 47
end_node_topo_uuid = 63
start_node_gdf = pois_gdf[pois_gdf['topo_uuid'] == start_node_topo_uuid]
end_node_gdf = pois_gdf[pois_gdf['topo_uuid'] == end_node_topo_uuid]
start_node = start_node_gdf.iloc[0]["geometry"]
end_node = end_node_gdf.iloc[0]["geometry"]
shortest_paths = OsmGt.shortest_path_from_location(
"Roanne",
[
(start_node, end_node),
(start_node, end_node), # duplicate pairs are cleaned
],
mode="pedestrian"
)
layers_to_add = [
{
"input_gdf": shortest_paths[["geometry"]],
"legend": "shortest_path",
"color": "red",
"line_width": 5
},
{
"input_gdf": start_node_gdf,
"legend": "source node",
"color": "blue",
"style": "circle",
"size": 9
},
{
"input_gdf": end_node_gdf,
"legend": "target node",
"color": "green",
"style": "circle",
"size": 9
},
]
my_shortest_path_map = Gdf2Bokeh(
"My shortest path - from OsmGT (https://github.com/amauryval)",
layers=layers_to_add
)
show(my_shortest_path_map.figure)
display(shortest_paths)
2020-12-11 18:51:20 - OsmGtShortestPath - INFO : Shortest path processing... 2020-12-11 18:51:20 - OsmGtShortestPath - INFO : From location: Roanne 2020-12-11 18:51:20 - OsmGtShortestPath - INFO : Loading data... 2020-12-11 18:51:20 - OsmGtShortestPath - INFO : NominatimApi: Query 200:OK in 0.33 sec. 2020-12-11 18:51:23 - OsmGtShortestPath - INFO : OverpassApi: Query 200:OK in 2.56 sec. 2020-12-11 18:51:23 - OsmGtShortestPath - INFO : Rebuild network data 2020-12-11 18:51:23 - OsmGtShortestPath - INFO : Network cleaning... 2020-12-11 18:51:23 - OsmGtShortestPath - INFO : Starting: Adding new nodes on the network 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Find nearest line for each node 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Split line 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Topology lines checker: to add: 2, to split: 2 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Starting: Find intersections 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Done: Find intersections 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Build lines 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Prepare graph 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Prepare GeoDataframe 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : GeoDataframe Ready 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Compute shortest path from 2396 to 2397 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Prepare GeoDataframe 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : GeoDataframe Ready 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : Prepare GeoDataframe 2020-12-11 18:51:24 - OsmGtShortestPath - INFO : GeoDataframe Ready
| source_node | target_node | osm_ids | osm_urls | geometry | id | |
|---|---|---|---|---|---|---|
| 0 | POINT (4.060419 46.0259) | POINT (4.055869 46.025223) | 125729891, 125729891, 125729891, 125729891, 12... | https://www.openstreetmap.org/way/125729891, h... | LINESTRING (4.06042 46.02590, 4.06044 46.02584... | 0 |
CPU times: user 986 ms, sys: 52.8 ms, total: 1.04 s Wall time: 4.26 s
%%time
start_node_topo_uuid = 47
source_node = pois_gdf[pois_gdf['topo_uuid'] == start_node_topo_uuid]
# 2 = 2 min ; 5 = 5 min ; 10 = 10 min
isochrones_polygon_values = {
2: "#d9ef8b",
5: "#fee08b",
10: "#f46d43"
}
isochrones_lines_values = {
2: "#668100",
5: "#e2a803",
10: "#962603"
}
trip_speed = 3 # km/h
location_point = source_node.iloc[0]["geometry"]
isochrones_polygons, isochrones_lines = OsmGt.isochrone_from_source_node(
location_point,
list(isochrones_polygon_values.keys()),
trip_speed,
mode="pedestrian"
)
isochrones_polygons["color"] = isochrones_polygons["iso_name"].map(lambda x: isochrones_polygon_values[x])
isochrones_lines["color"] = isochrones_lines["iso_name"].map(lambda x: isochrones_lines_values[x])
layers_to_add = [
{
"input_gdf": isochrones_polygons,
"legend": "iso_name",
"fill_color": "color",
"line_color": "color",
"fill_alpha": 0.5
},
{
"input_gdf": source_node,
"legend": "Source node",
"style": "circle",
"color": "red",
"size": 5
},
{
"input_gdf": isochrones_lines,
"legend": "iso_name",
"color": "color",
"line_width": 2
},
]
my_shortest_path_map = Gdf2Bokeh(
"Isochrones from times - from OsmGT (https://github.com/amauryval)",
layers=layers_to_add
)
show(my_shortest_path_map.figure)
print("\nIsochrones polygons output")
display(isochrones_polygons)
print("\nIsochrones lines output")
display(isochrones_lines.head(2))
2020-12-11 18:51:24 - OsmGtIsochrone - INFO : Isochrone processing... 2020-12-11 18:51:24 - OsmGtIsochrone - INFO : From bbox: (4.054579950653223, 46.02184560592687, 4.066258049346776, 46.02995409673327) 2020-12-11 18:51:24 - OsmGtIsochrone - INFO : Loading data... 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : OverpassApi: Query 200:OK in 1.04 sec. 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : Rebuild network data 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : Network cleaning... 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : Starting: Adding new nodes on the network 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : Find nearest line for each node 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : Split line 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : Topology lines checker: to add: 1, to split: 1 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : Starting: Find intersections 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : Done: Find intersections 2020-12-11 18:51:26 - OsmGtIsochrone - INFO : Build lines 2020-12-11 18:51:27 - OsmGtIsochrone - INFO : OverpassApi: Query 200:OK in 1.58 sec. 2020-12-11 18:51:27 - OsmGtIsochrone - INFO : Prepare GeoDataframe 2020-12-11 18:51:27 - OsmGtIsochrone - INFO : GeoDataframe Ready 2020-12-11 18:51:27 - OsmGtIsochrone - INFO : Prepare graph 2020-12-11 18:51:28 - OsmGtIsochrone - INFO : Compute isochrone: 10 minutes => 500 meters 2020-12-11 18:51:28 - OsmGtIsochrone - INFO : Compute isochrone: 5 minutes => 250 meters 2020-12-11 18:51:28 - OsmGtIsochrone - INFO : Compute isochrone: 2 minutes => 100 meters 2020-12-11 18:51:29 - OsmGtIsochrone - INFO : GeoDataframe Ready
ERROR:bokeh.core.validation.check:E-1006 (NON_MATCHING_DATA_SOURCES_ON_LEGEND_ITEM_RENDERERS): LegendItem.label is a field, but renderer data sources don't match: LegendItem(id='1824', ...)
Isochrones polygons output
| iso_name | time_unit | iso_distance | distance_unit | geometry | id | color | |
|---|---|---|---|---|---|---|---|
| 0 | 10 | minutes | 500 | meters | POLYGON ((4.06037 46.02408, 4.06044 46.02402, ... | 0 | #f46d43 |
| 1 | 5 | minutes | 250 | meters | POLYGON ((4.05775 46.02495, 4.05775 46.02495, ... | 1 | #fee08b |
| 2 | 5 | minutes | 250 | meters | POLYGON ((4.06072 46.02710, 4.06075 46.02708, ... | 2 | #fee08b |
| 3 | 5 | minutes | 250 | meters | POLYGON ((4.06039 46.02576, 4.06039 46.02576, ... | 3 | #fee08b |
| 4 | 5 | minutes | 250 | meters | POLYGON ((4.06045 46.02579, 4.06044 46.02578, ... | 4 | #fee08b |
| 5 | 2 | minutes | 100 | meters | POLYGON ((4.05932 46.02531, 4.05926 46.02534, ... | 5 | #d9ef8b |
Isochrones lines output
| highway | name | id | osm_url | topo_uuid | topology | oneway | maxspeed | ref | source:maxspeed | ... | service | maxweight | access | cycleway:left | oneway:bicycle | footway | iso_name | iso_distance | geometry | color | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 6 | residential | Rue du Moulin à Vent | 146561875 | https://www.openstreetmap.org/way/146561875 | 13_36 | split | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | 10.0 | 500.0 | LINESTRING (4.05963 46.02322, 4.05975 46.02331... | #962603 |
| 8 | secondary | Boulevard Thiers | 147373495 | https://www.openstreetmap.org/way/147373495 | 14_08 | split | yes | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | 10.0 | 500.0 | LINESTRING (4.05699 46.02509, 4.05705 46.02506... | #962603 |
2 rows × 27 columns
CPU times: user 2.41 s, sys: 199 ms, total: 2.61 s Wall time: 4.87 s
%%time
start_node_topo_uuid = 47 # change the value here to define a new source point to compute isochrones
source_node = pois_gdf[pois_gdf['topo_uuid'] == start_node_topo_uuid]
trip_speed = 3 # km/h
isochrones_polygon_values = {
250: "#d9ef8b",
500: "#fee08b",
750: "#f46d43",
1000: "#8B4513"
}
# 250 = 250 meters ; 500 = 500 meters ; etc
isochrones_lines_values = {
250: "#668100",
500: "#e2a803",
750: "#962603",
1000: "#000000"
}
location_point = source_node.iloc[0]["geometry"]
isochrones_polygons, isochrones_lines = OsmGt.isochrone_distance_from_source_node(
location_point,
list(isochrones_polygon_values.keys()), # meters
trip_speed,
mode="pedestrian"
)
isochrones_polygons["color"] = isochrones_polygons["iso_distance"].map(lambda x: isochrones_polygon_values[x])
isochrones_lines["color"] = isochrones_lines["iso_distance"].map(lambda x: isochrones_lines_values[x])
layers_to_add = [
{
"input_gdf": isochrones_polygons,
"legend": "iso_distance",
"fill_color": "color",
"line_color": "color",
"fill_alpha": 0.5
},
{
"input_gdf": isochrones_lines,
"legend": "iso_distance",
"color": "color",
"line_width": 2
},
{
"input_gdf": source_node,
"legend": "Source node",
"style": "circle",
"color": "red",
"size": 15
},
]
my_shortest_path_map = Gdf2Bokeh(
"Isochrones from distance - from OsmGT (https://github.com/amauryval)",
layers=layers_to_add
)
show(my_shortest_path_map.figure)
2020-12-11 18:51:29 - OsmGtIsochrone - INFO : Isochrone processing... 2020-12-11 18:51:29 - OsmGtIsochrone - INFO : From bbox: (4.048740901306446, 46.017790914512375, 4.072097098693553, 46.03400789612821) 2020-12-11 18:51:29 - OsmGtIsochrone - INFO : Loading data... 2020-12-11 18:51:30 - OsmGtIsochrone - INFO : OverpassApi: Query 200:OK in 1.03 sec. 2020-12-11 18:51:30 - OsmGtIsochrone - INFO : Rebuild network data 2020-12-11 18:51:30 - OsmGtIsochrone - INFO : Network cleaning... 2020-12-11 18:51:31 - OsmGtIsochrone - INFO : Starting: Adding new nodes on the network 2020-12-11 18:51:31 - OsmGtIsochrone - INFO : Find nearest line for each node 2020-12-11 18:51:31 - OsmGtIsochrone - INFO : Split line 2020-12-11 18:51:31 - OsmGtIsochrone - INFO : Topology lines checker: to add: 1, to split: 1 2020-12-11 18:51:31 - OsmGtIsochrone - INFO : Starting: Find intersections 2020-12-11 18:51:31 - OsmGtIsochrone - INFO : Done: Find intersections 2020-12-11 18:51:31 - OsmGtIsochrone - INFO : Build lines 2020-12-11 18:51:32 - OsmGtIsochrone - INFO : OverpassApi: Query 200:OK in 0.97 sec. 2020-12-11 18:51:32 - OsmGtIsochrone - INFO : Prepare GeoDataframe 2020-12-11 18:51:32 - OsmGtIsochrone - INFO : GeoDataframe Ready 2020-12-11 18:51:32 - OsmGtIsochrone - INFO : Prepare graph 2020-12-11 18:51:34 - OsmGtIsochrone - INFO : Compute isochrone: 20.0 minutes => 1000 meters 2020-12-11 18:51:34 - OsmGtIsochrone - INFO : Compute isochrone: 15.0 minutes => 750 meters 2020-12-11 18:51:34 - OsmGtIsochrone - INFO : Compute isochrone: 10.0 minutes => 500 meters 2020-12-11 18:51:34 - OsmGtIsochrone - INFO : Compute isochrone: 5.0 minutes => 250 meters 2020-12-11 18:51:39 - OsmGtIsochrone - INFO : GeoDataframe Ready
ERROR:bokeh.core.validation.check:E-1006 (NON_MATCHING_DATA_SOURCES_ON_LEGEND_ITEM_RENDERERS): LegendItem.label is a field, but renderer data sources don't match: LegendItem(id='2036', ...)
CPU times: user 9.6 s, sys: 78.3 ms, total: 9.68 s Wall time: 10.5 s